<template>
  <el-form-item
    class="merak-control"
    ref="itemRef"
    for="#"
    :prop="props.field"
    :label="props.title"
    :required="!!props.required"
    :rules="rules"
    :error="props.error"
  >
    <template #label="{ label }">
      <div class="merak-control-tip">
        <span>{{ label }}</span>
        <el-tooltip v-if="props.tip" popper-class="merak-control-tip-popper" placement="top">
          <el-icon>
            <info-filled />
          </el-icon>
          <template #content>
            <div v-html="props.tip" />
          </template>
        </el-tooltip>
      </div>
    </template>
    <template #error="{ error }">
      <slot :name="`${props.field}_error`" :error="error" />
    </template>
    <template v-if="!!props.custom">
      <slot :name="props.field" />
    </template>
    <template v-else-if="!!props.onlyView">
      <component :is="renderView" />
    </template>
    <!-- 输入框 -->
    <el-input
      v-else-if="props.type === 'input'"
      class="qf_input"
      v-model="model"
      :type="props.native"
      :placeholder="props.placeholder"
      :maxlength="props.maxlength ?? 200"
      :rows="props.rows ?? 3"
      :readonly="!!props.readonly"
      :disabled="!!props.disabled"
      :style="{ '--merak-control-input-align': !!props.suffix ? 'right' : 'left' }"
      @keydown="handleInputKeyDown"
      @input="handleInputEvent"
      @mousewheel="(e:Event)=> e.preventDefault()"
      @change="handleInputChange"
    >
      <template #prefix>
        <slot v-if="Object.keys($slots).includes(`${props.field}_prefix`)" :name="`${props.field}_prefix`" />
        <span v-else-if="!!props.prefix && typeof props.prefix === 'string'">{{ props.prefix }}</span>
        <component v-else-if="!!props.prefix" :is="props.prefix" />
      </template>
      <template #suffix>
        <slot v-if="Object.keys($slots).includes(`${props.field}_suffix`)" :name="`${props.field}_suffix`" />
        <span v-else-if="!!props.suffix && typeof props.suffix === 'string'">{{ props.suffix }}</span>
        <component v-else-if="!!props.suffix" :is="props.suffix" />
      </template>
    </el-input>
    <!-- 下拉 -->
    <el-select
      v-else-if="props.type === 'select'"
      ref="selectRef"
      class="qf_select"
      v-model="model"
      :loading="state.loading"
      :multiple="!!props.multiple"
      :remote="!!props.remote"
      :remote-show-suffix="!!props.remote"
      :remote-method="handleRemoteMethod"
      :disabled="!!props.disabled"
      :placeholder="props.placeholder"
      clearable
      filterable
      @change="value => emits('change', props.field, value, options)"
      @visible-change="handleSelectVisibleChange"
    >
      <template v-if="options.length === 0 && validValue(model) && validValue(props.defaultSelectedLabel)">
        <template v-if="!!props.multiple">
          <el-option
            v-for="(value, index) in [...(model ?? [])]"
            :value="value"
            :label="props.defaultSelectedLabel?.split(',')?.[index]"
          />
        </template>
        <el-option :value="model" :label="props.defaultSelectedLabel" />
      </template>
      <template v-else>
        <el-option v-for="option in options" :value="option.value" :label="option.label" :disabled="!!option.disabled" />
      </template>
    </el-select>
    <!-- 单选 -->
    <el-radio-group
      v-else-if="props.type === 'radio'"
      v-model="model"
      :disabled="!!props.disabled"
      @change="value => emits('change', props.field, value, options)"
    >
      <el-radio v-for="option in options" :value="option.value" :label="option.label" :disabled="!!option.disabled" />
    </el-radio-group>
    <!-- 日期选择 -->
    <el-date-picker
      v-else-if="props.type === 'date'"
      ref="dateRef"
      class="qf_date"
      v-model="model"
      :type="!!props.range ? 'daterange' : 'date'"
      :placeholder="props.placeholder"
      :start-placeholder="props.startPlaceholder"
      :end-placeholder="props.endPlaceholder"
      :range-separator="$t('merak.rangto')"
      :format="renderFormat(props.format ?? 'YYYY-MM-DD')"
      :value-format="props.format ?? 'YYYY-MM-DD'"
      :readonly="!!props.readonly"
      :disabled="!!props.disabled"
      :disabled-date="props.disabledDate"
      @change="(value: any) => emits('change', props.field, value)"
      @visible-change="(open:boolean) => (panelOpen = open)"
    />
  </el-form-item>
</template>

<script setup lang="tsx" name="merak-control">
import type { MerakControlProps, MerakControlOption } from "./index";

import { getCurrentInstance, ref, reactive, computed, watchEffect, onMounted } from "vue";

import { ElFormItem, ElInput, ElSelect, ElOption, ElRadioGroup, ElRadio, ElDatePicker, ElTooltip, ElIcon } from "element-plus";

import { InfoFilled } from "@element-plus/icons-vue";

import useDateFormat from "@/hooks/useDateFormat";

const props = defineProps<MerakControlProps>();

const emits = defineEmits<{
  change: [field: string, value: any, options?: MerakControlOption[]];
}>();

const model = defineModel<any>({ required: true });

const currentInstance = getCurrentInstance();

const { renderFormat } = useDateFormat();

const itemRef = ref<InstanceType<typeof ElFormItem>>();

const selectRef = ref<InstanceType<typeof ElSelect>>();

const dateRef = ref<InstanceType<typeof ElDatePicker>>();

const panelOpen = ref(false);

const state = reactive<{
  loading: boolean;
  options: MerakControlOption[];
  disabledOptionKeys: Array<string | number>;
  disabledInvertKeys: Array<string | number>;
}>({
  loading: false,
  options: [],
  disabledOptionKeys: [],
  disabledInvertKeys: []
});

const message = computed(() => {
  if (!!props.message) {
    return props.message;
  }
  switch (props.type) {
    case "input":
      return `${currentInstance?.proxy?.$t("merak.input_placeholder")}${props.title}`;
    default:
      return `${currentInstance?.proxy?.$t("merak.placeholder_select")}${props.title}`;
  }
});

const rules = computed(() => {
  if (!!props.rules?.length) {
    return [...(props.rules ?? [])];
  }
  if (props.required) {
    return [{ required: true, message: message.value }];
  }
  return;
});

const options = computed<MerakControlOption[]>(() => {
  const arr: MerakControlOption[] = [];
  if (props.type === "select" && !!props.remote) {
    arr.push(...state.options);
  } else if (props.type === "select" || props.type === "radio") {
    if (!!props.options?.length) {
      arr.push(...props.options);
    } else {
      arr.push(...state.options);
    }
  }

  return arr.map(option => ({
    ...option,
    disabled: !!state.disabledOptionKeys.length
      ? state.disabledOptionKeys.includes(option.value)
      : !!state.disabledInvertKeys.length
        ? !state.disabledInvertKeys.includes(option.value)
        : option.disabled
  }));
});

// 验证有效值
const validValue = (value: any) => typeof value === "number" || !!value;

// 渲染只读文本
const renderView = () => {
  switch (props.type) {
    case "select":
      if (!!props.multiple) {
        const filterOptions = options.value.filter(option => [...(model.value ?? [])].includes(option.value));
        return (
          <span>{!!options.value.length ? filterOptions.map(item => item.label).join(",") : props.defaultSelectedLabel}</span>
        );
      }
      const findSelectOption = options.value.find(option => option.value === model.value);
      return <span>{!!options.value.length ? findSelectOption?.label : props.defaultSelectedLabel}</span>;
    case "radio":
      const findRadioOption = options.value.find(option => option.value === model.value);
      return <span>{!!options.value.length ? findRadioOption?.label : props.defaultSelectedLabel}</span>;
    case "date":
      if (!!props.range) {
        const [startDate, endDate] = model.value ?? [];
        return (
          <div class="merak-control-range">
            <span>{startDate}</span>
            <span>{currentInstance?.proxy?.$t("merak.rangto")}</span>
            <span>{endDate}</span>
          </div>
        );
      }
      return <span>{model.value}</span>;
    case "input":
      if (!!props.suffix && typeof props.suffix === "string") {
        return <span>{model.value + props.suffix}</span>;
      }
      if (["spu", "sku"].includes(props.field)) {
        return <span>{props.defaultSelectedLabel ?? model.value}</span>;
      }
      return <span>{model.value}</span>;
    default:
      return <span>{model.value}</span>;
  }
};

// Input 优化输入
const handleInputKeyDown = (event: Event) => {
  if (props.type !== "input") return;
  const { key, ctrlKey, metaKey, code } = event as KeyboardEvent;
  const selectionStart = (event.target as HTMLInputElement).selectionStart ?? 0;
  const inputValue = (event.target as HTMLInputElement).value ?? "";
  // 数字输入框
  if (props.native === "number") {
    // 系统按键
    if (
      [
        "Delete",
        "Backspace",
        "Tab",
        "Esc",
        "Escape",
        "Enter",
        "Home",
        "End",
        "PageUp",
        "PageDown",
        "Del",
        "Delete",
        "Left",
        "ArrowLeft",
        "Right",
        "ArrowRight",
        "Insert",
        "Up",
        "ArrowUp",
        "Down",
        "ArrowDown"
      ].includes(key)
    ) {
      return;
    }
    // ctrl 配合按键
    if ((ctrlKey || metaKey) && ["a", "A", "c", "C", "x", "X", "v", "V"].includes(key)) {
      return;
    }
    // 数字输入键
    if (key >= "0" && key <= "9") {
      return;
    }
    // 负数
    if (selectionStart === 0 && (key === "-" || code === "Minus")) {
      return;
    }
    // 小数位
    if (!inputValue.includes(".") && (key === "." || code === "Period")) {
      return;
    }
    event.preventDefault();
  }
  // 开头不允许输入空格
  if (selectionStart === 0 && (key === " " || code === "Space")) {
    event.preventDefault();
  }
  // 不允许换行
  if (key === "Enter" || code === "Enter") {
    event.preventDefault();
  }
};

// Input 输入事件
const handleInputEvent = async (value: string) => {
  if (props.type !== "input") return;
  if (!!props.maxlength) {
    model.value = value.slice(0, props.maxlength);
  }
  if (!!props.validValue) {
    model.value = await props.validValue(value);
  }
};

// Input 触发事件
const handleInputChange = (value: string) => {
  if (props.type !== "input") return;
  model.value = value.trim();
  emits("change", props.field, model.value);
};

// 统一处理选项查询
const handleQueryOptions = (query: string = "") => {
  const timer = requestAnimationFrame(async () => {
    state.loading = true;
    await props.queryOptions?.(props.field, query).then(options => {
      state.options = [...(options ?? [])].filter(option => option.label.toLowerCase().includes(query.toLowerCase()));
      return;
    });
    state.loading = false;
    cancelAnimationFrame(timer);
  });
};

// Select 远程获取搜索选项
const handleRemoteMethod = async (query: string) => {
  if (props.type !== "select") {
    return;
  }
  if (!query) {
    state.options = [];
    return;
  }
  if (!!props.options?.length) {
    state.options = [...props.options].filter(option => option.label.toLowerCase().includes(query.toLowerCase()));
    return;
  }
  handleQueryOptions(query);
};

// 下拉框展开收起事件
const handleSelectVisibleChange = (open: boolean) => {
  panelOpen.value = open;
  if (open && props.type === "select" && !props.remote && !!props.async && options.value.length === 0) {
    handleQueryOptions();
  }
};

// 兼容下拉选项枚举与初始化数据类型不符的情况
watchEffect(() => {
  if ((props.type === "select" && !!props.multiple) || options.value.length === 0 || !model.value) return;
  const type = typeof options.value.at(0)?.value;
  if (!!model.value && (typeof model.value === "string" || typeof model.value === "number") && typeof model.value !== type) {
    switch (type) {
      case "string":
        model.value = model.value.toString();
        break;
      case "number":
        model.value = Number(model.value);
      default:
        break;
    }
  }
});

// 初始化选项
onMounted(async () => {
  if (!!options.value.length) return;
  if ((props.type === "select" && !props.remote && !props.async) || props.type === "radio") {
    handleQueryOptions();
  }
});

// 暴露到组件 ref
defineExpose({
  options,
  // 禁用当前选项
  disableOptions: (keys: Array<string | number>) => (state.disabledOptionKeys = [...keys]),
  // 禁用反选选项
  disableInvertOptions: (keys: Array<string | number>) => (state.disabledInvertKeys = [...keys]),
  // 重置表单项
  resetField: (resetValue: boolean = false) => {
    if (resetValue) {
      model.value = undefined;
      itemRef.value?.clearValidate();
    } else {
      itemRef.value?.resetField();
    }
  },
  // 校验表单项
  validate: (trigger: "blur" | "change" = "change") => itemRef.value?.validate(trigger),
  // 移除表单项校验结果
  clearValidate: () => itemRef.value?.clearValidate(),
  // 关闭下拉框
  closePopover: () => {
    if (!panelOpen.value) return;
    switch (props.type) {
      case "select":
        selectRef.value?.blur();
        break;
      case "date":
        // @ts-ignore
        dateRef.value?.handleClose();
        break;
      default:
        break;
    }
  }
});
</script>

<style lang="scss">
.merak-control-tip-popper {
  --el-text-color-primary: rgba(48, 49, 51, 0.8);
  max-width: 25vw;
  border: none !important;
  & > .el-popper__arrow::before {
    width: 0;
    height: 0;
    background-color: transparent !important;
    border-width: 5px !important;
  }
  & > div {
    white-space: pre-wrap;
    & > * {
      margin: 0;
      text-indent: 2em;
    }
    & > :not(:first-child) {
      margin-top: 5px;
    }
  }
}
.dark .merak-control-tip-popper {
  --el-text-color-primary: #e5eaf3;
}

.merak-control {
  &-tip {
    display: inline-flex;
    align-items: center;
    cursor: pointer;
    .el-icon {
      margin-left: 5px;
      color: var(--el-color-primary);
      font-size: 14px;
    }
  }
  .el-form-item__content > * {
    width: 100%;
  }
  .el-input__wrapper {
    height: var(--el-input-height) !important;
    font-size: var(--merak-text-size);
    box-sizing: border-box;
    .el-input__inner {
      line-height: 1;
      text-align: var(--merak-control-input-align);
    }
  }
  input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
    &::-webkit-inner-spin-button,
    &::-webkit-outer-spin-button {
      -webkit-appearance: none;
    }
  }
}
</style>
